#include <algorithm>
#include <cassert>
#include <cstring>
#include <functional>
#include <numeric>

#include "Output.h"
#include "Source.h"
#include "Suite.h"

using namespace std;

namespace uav
{
namespace test
{

namespace
{
	// Destroys all dynamically allocated objects within the given range.
	//
	template <class FwdIter>
	void
	destroy_range(FwdIter first, FwdIter last)
	{
		while (first != last)
			delete *first++;
	}

} // anonymous namespace

/// Constructs an empty test suite.
///
Suite::Suite()
:	_cur_test(0),
	_output(0),
	_success(true)
{}

/// Destroys this suite object.
///
Suite::~Suite()
{
	destroy_range(_suites.begin(), _suites.end());
}

/// Starts the testing. All tests in this suite and embedded suites will
/// be executed.
///
/// \param output          Progress report destination.
/// \param cont_after_fail Continue functions despite failures.
///
/// \return True if no test failed; false otherwise.
///
bool
Suite::run(Output& output, bool cont_after_fail)
{
	int ntests = total_tests();
	output.initialize(ntests);
	do_run(&output, cont_after_fail);
	output.finished(ntests, total_time(true));
	return _success;
}

/// \fn void Suite::setup()
///
/// Setups a test fixture. This function is called before each test,
/// in this suite, is executed.
///
/// This function should be overloaded by derived classes to provide
/// specialized behavior.
///
/// \see tear_down()

/// \fn void Suite::tear_down()
///
/// Tears down a test fixture. This function is called after each test,
/// in this suite, have been executed.
///
/// This function should be overloaded by derived classes to provide
/// specialized behavior.
///
/// \see setup()

/// Adds a suite to this suite. Tests in added suites will be executed
/// when run() of the top-level suite is called.
///
/// \param suite %Test suite to add.
///
void
Suite::add(auto_ptr<Suite> suite)
{
	_suites.push_back(suite.release());
}

/// Registers a test function.
///
/// \b Note: Do not call this function directly, use the TEST_ADD(func)
/// macro instead.
///
/// \param func Pointer to a test function.
/// \param name Class and function name of the function. The format \b must
///             equal \e class::func.
///
void
Suite::register_test(Func func, const string& name)
{
	string::size_type pos = name.find_first_of(':');
	assert(!name.empty() && name[pos + 1] == ':' && name[pos + 2] != '\0');

	_name.assign(name, 0, pos);
	_tests.push_back(Data(func, name.substr(pos + 2)));
}

/// Issues an assertment to the output handler.
///
/// Do not call this function directly, use one of the available assertment
/// macros instead, see \ref asserts.
///
/// \param s Assert point information.
///
void
Suite::assertment(Source s)
{
	s._suite = _name;
	s._test  = *_cur_test;
	_output->assertment(s);
	_result = _success = false;
}

// Functor to execute tests for the given suite.
//
struct Suite::ExecTests
{
	Suite& _suite;

	ExecTests(Suite& s) : _suite(s) {}

	void operator()(Data& data)
	{
		_suite._cur_test = &data._name;
		_suite._result = true; // assume success, assert will set to false
		_suite._output->test_start(data._name);

		_suite.setup();
		Time start(Time::current());
		// FIXME Also feedback exception to user
		try
		{
			(_suite.*data._func)();
		} catch (...) {
			_suite._result = false;
		}
		Time end(Time::current());
		_suite.tear_down();

		data._time = end - start;
		_suite._output->test_end(data._name, _suite._result, data._time);
	}
};

// Functor to execute a suite.
//
struct Suite::DoRun
{
	bool	_continue;
	Output* _output;

	DoRun(Output* output, bool cont) : _continue(cont), _output(output) {}
	void operator()(Suite* suite) { suite->do_run(_output, _continue); }
};

// Execute all tests in this and added suites.
//
void
Suite::do_run(Output* os, bool cont_after_fail)
{
	_continue = cont_after_fail;
	_output = os;

	_output->suite_start(_tests.size(), _name);
	for_each(_tests.begin(), _tests.end(), ExecTests(*this));
	_output->suite_end(_tests.size(), _name, total_time(false));

	for_each(_suites.begin(), _suites.end(), DoRun(_output, _continue));

	// FIXME Find a cleaner way
	Suites::const_iterator iter = _suites.begin();
	while (iter != _suites.end())
	{
		if (!(*iter)->_success)
		{
			_success = false;
			break;
		}
		iter++;
	}
}

// Functor to count all tests in a suite.
//
struct Suite::SubSuiteTests
{
	int operator()(size_t value, const Suite* s) const
	{
		return value + s->total_tests();
	}
};

// Counts all tests in this and all its embedded suites.
//
int
Suite::total_tests() const
{
	return accumulate(_suites.begin(), _suites.end(),
					  _tests.size(), SubSuiteTests());
}

// Functor to accumulate execution time for tests.
//
struct Suite::SuiteTime
{
	Time operator()(const Time& time, const Data& data)
	{
		return time + data._time;
	}
};

// Functor to accumulate execution time for suites.
//
struct Suite::SubSuiteTime
{
	Time operator()(Time time, const Suite* s) const
	{
		return time + s->total_time(true);
	}
};

// Counts time accumulated execution time for all tests in this and all
// its embedded suites.
//
Time
Suite::total_time(bool recursive) const
{
	Time time = accumulate(_tests.begin(), _tests.end(),
						   Time(), SuiteTime());

	return !recursive ? time : accumulate(_suites.begin(), _suites.end(),
										  time, SubSuiteTime());
}

}}
